#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "ota_log.h"
#include "aos/kernel.h"
#include "aos/hal/flash.h"
#include "ota_import.h"
#include "ota_hal_os.h"
#include "ota_hal_ctrl.h"
#include "ota_import.h"
#include "ota_mcu_upgrade.h"

unsigned char *ota_cache           = NULL;
unsigned int ota_cache_len         = 0;
unsigned int ota_fw_size           = 0;
unsigned int ota_receive_total_len = 0;
static int boot_part               = HAL_PARTITION_OTA_TEMP;
static ota_crc16_ctx  ctx          = {0};

#if defined OTA_CONFIG_BLE
static unsigned int   has_erase_page_numbs = 0;
static unsigned int   total_erase_page_numbs = 0;
extern unsigned int hal_flash_erase_sector_size(void);
void xp_wdg_feed(void *arg1,void *arg2);

unsigned int ota_ble_align_to_page(unsigned int val, unsigned int page_size)
{
    return ((val + page_size - 1) & ~(page_size - 1));
}

int ota_ble_breakpoint_process(unsigned int image_size, unsigned int *break_point)
{
    int ret = 0;
    unsigned int num_pages = 0;
    unsigned int erase_sector_size = 0;
    if(break_point == NULL) {
        return -1;
    }
    erase_sector_size = hal_flash_erase_sector_size();
    if(erase_sector_size == 0) {
        return -1;
    }
    if(image_size != *break_point) {
        *break_point = *break_point & ~(erase_sector_size - 1);
    }
    num_pages = ota_ble_align_to_page(image_size - *break_point, erase_sector_size) / erase_sector_size;
    if (num_pages > 0) {
        has_erase_page_numbs   = *break_point / erase_sector_size;
        total_erase_page_numbs = num_pages + has_erase_page_numbs;
        ret = hal_flash_erase(boot_part, *break_point, erase_sector_size);
        if (ret < 0) {
            OTA_LOG_E(" f-erase failed");
            ret = -1;
        }
        else {
            has_erase_page_numbs++;
        }
    }
    return ret;
}

OTA_WEAK int ota_ble_write(unsigned int *off, char *in_buf, int in_buf_len)
{
    int ret = -1;
    unsigned int erase_sector_size = 0;
    erase_sector_size = hal_flash_erase_sector_size();
    if(erase_sector_size != 0) {
        ret = 0;
        if((has_erase_page_numbs <= total_erase_page_numbs) &&
           (*off % erase_sector_size + in_buf_len >= erase_sector_size)) {
            ret = hal_flash_erase(boot_part, *off + in_buf_len, erase_sector_size);
            if(ret == 0) {
                has_erase_page_numbs++;
            }
        }
        if(ret == 0) {
            ret = hal_flash_write(boot_part, off, in_buf, in_buf_len);
        }
    }
    return ret;
}

OTA_WEAK int ota_ble_check_upgrade_result(void)
{
    int ret                 = -1;
    int ota_result          = -1;
    unsigned int offset     = 0;
    ota_boot_param_t param;
    memset(&param, 0, sizeof(ota_boot_param_t));
    ret = hal_flash_read(HAL_PARTITION_PARAMETER_1, &offset, &param, sizeof(ota_boot_param_t));
    if(ret >= 0) {
        if((param.boot_count != 0) && (param.boot_count != 0xff)) {
            char *sys_ver = ota_hal_version(0, NULL);
            if(sys_ver != NULL) {
                ota_result = strncmp(param.ver, sys_ver, strlen(sys_ver));
            }
        }
    }
    return ota_result;
}
#endif

OTA_WEAK int ota_hal_init(ota_boot_param_t *param)
{
    int ret = OTA_INIT_FAIL;
    if(param != NULL) {
        ret = 0;
        hal_logic_partition_t  part_info;
        hal_logic_partition_t *p_part_info;
        p_part_info = &part_info;
        memset(p_part_info, 0, sizeof(hal_logic_partition_t));
        ret = hal_flash_info_get(boot_part, p_part_info);
        if(ret != 0 || param->len == 0) {
            ret = OTA_INIT_FAIL;
        }
        else {
            ota_receive_total_len = 0;
            ota_fw_size = param->len;
            ota_cache = ota_malloc(OTA_FLASH_WRITE_CACHE_SIZE);
            if (NULL == ota_cache) {
                ret = OTA_INIT_FAIL;
            }
            else {
                if((param->upg_magic == OTA_BIN_MAGIC_MCU)&&(param->len > p_part_info->partition_length)) {
                    ret = ota_mcu_hal_init();
                    if(ret < 0) {
                        ret = OTA_INIT_FAIL;
                    }
                }
            }
            ota_crc16_init(&ctx);
        }
        OTA_LOG_I("ota init part:%d len:%d \n", boot_part, param->len);
    }
    if(ret != 0) {
        if(NULL != ota_cache) {
            ota_free(ota_cache);
            ota_cache = NULL;
        }
        OTA_LOG_E("ota init fail!");
    }
    return ret;
}

OTA_WEAK int ota_hal_write(unsigned int *off, char *in_buf, unsigned int in_buf_len)
{
    int ret = 0;
    int tocopy = 0;
    unsigned int len = 0;
    unsigned int erase_off = 0;
    unsigned int block_size = 0;
    unsigned int offset = 0;
    static int erase_flag = 0;
    hal_logic_partition_t  part_info = {0};

    if(off == NULL || in_buf_len > OTA_FLASH_WRITE_CACHE_SIZE) {
        return OTA_UPGRADE_WRITE_FAIL;
    }
    if (in_buf_len <= OTA_FLASH_WRITE_CACHE_SIZE - ota_cache_len) {
        tocopy = in_buf_len;
    } else {
        tocopy = OTA_FLASH_WRITE_CACHE_SIZE - ota_cache_len;
    }
    /*Start from last byte of remaing data*/
    memcpy(ota_cache + ota_cache_len, in_buf, tocopy);
    ota_cache_len += tocopy;
    if (ota_cache_len == OTA_FLASH_WRITE_CACHE_SIZE) 
    {
        hal_flash_info_get(boot_part, &part_info);
        len = part_info.partition_length;
        if (erase_flag == 0) 
        {
            while(len > 0) 
            {
                block_size = (len > OTA_FLASH_BLOCK_SIZE)? OTA_FLASH_BLOCK_SIZE: len; 
                ret = hal_flash_erase(boot_part,  erase_off, block_size);
                if(ret < 0) {
                    ret = OTA_INIT_FAIL;
                    OTA_LOG_E("erase fail! ");
                    goto EXIT;
                }
                erase_off += block_size;
                len -= block_size;
                ota_msleep(10);
                xp_wdg_feed(NULL,NULL);
            }
            erase_flag = 1;
        }

        if(*off > part_info.partition_length) {
            offset = *off - part_info.partition_length;
            ret = ota_mcu_hal_write(&offset, ota_cache, OTA_FLASH_WRITE_CACHE_SIZE);
        } else {
            ret = hal_flash_write(boot_part, off, ota_cache, OTA_FLASH_WRITE_CACHE_SIZE);
        }
        xp_wdg_feed(NULL,NULL);
        if(*off <= part_info.partition_length)
            ota_crc16_update(&ctx, ota_cache, OTA_FLASH_WRITE_CACHE_SIZE);
        if(ret < 0) {
            goto EXIT;
        }
    }
    /*keep remaining data*/
    if (in_buf_len - tocopy > 0) {
        /*Now ota_cache only contains remaing data*/
        memcpy(ota_cache, in_buf + tocopy, in_buf_len - tocopy);
        ota_cache_len = in_buf_len - tocopy;
    }
    ota_receive_total_len += in_buf_len;
    if(ota_receive_total_len == ota_fw_size) {
        if (ota_fw_size != 0) 
        {
            hal_flash_info_get(boot_part, &part_info);
            len = part_info.partition_length;
            if (erase_flag == 0) 
            {
                while(len > 0) 
                {
                    block_size = (len > OTA_FLASH_BLOCK_SIZE)? OTA_FLASH_BLOCK_SIZE: len; 
                    ret = hal_flash_erase(boot_part,  erase_off, block_size);
                    if(ret < 0) {
                        ret = OTA_INIT_FAIL;
                        OTA_LOG_E("erase fail! ");
                        goto EXIT;
                    }
                    erase_off += block_size;
                    len -= block_size;
                    ota_msleep(10);
                    xp_wdg_feed(NULL,NULL);
                }
                erase_flag = 1;
            }
            if(*off > part_info.partition_length) {
                offset = *off - part_info.partition_length;
                ret = ota_mcu_hal_write(&offset, ota_cache, ota_cache_len);
                *off += OTA_FLASH_WRITE_CACHE_SIZE;
            } else {
                ret = hal_flash_write(boot_part, off, ota_cache, ota_cache_len);
                OTA_LOG_E("\r\n ===write flash off %d len %d =====\r\n", *off, ota_cache_len);
            }
            xp_wdg_feed(NULL,NULL);
            if(*off <= part_info.partition_length) 
            {
                OTA_LOG_E("\r\n === enter ota crc calc =====\r\n");
                ota_crc16_update(&ctx, ota_cache, ota_cache_len);
                OTA_LOG_E("\r\n === leave ota crc calc 0x%x =====\r\n",ctx.crc);
            }  
        }
        if(ota_cache != NULL) {
            ota_free(ota_cache);
            ota_cache = NULL;
        }
    }
EXIT:
    if(ret < 0) {
        if(ota_cache != NULL) {
            ota_free(ota_cache);
            ota_cache = NULL;
        }
        OTA_LOG_E("ota_write err:%d", ret);
        erase_flag = 0;
    }
    return ret;
}

OTA_WEAK int ota_hal_rollback(void)
{
    int ret = 0;
    unsigned int offset = 0;
    ota_boot_param_t param;
    memset(&param, 0, sizeof(ota_boot_param_t));
    ret = hal_flash_read(HAL_PARTITION_PARAMETER_1, &offset, &param, sizeof(ota_boot_param_t));
    if(ret < 0) {
        OTA_LOG_E("rollback err:%d", ret);
        return ret;
    }
    if((param.boot_count != 0) && (param.boot_count != 0xff)) {
        param.upg_flag = 0;
        param.boot_count = 0; /*Clear bootcount to avoid rollback*/
        ret = ota_update_parameter(&param);
        ret |= ota_parameter_save(NULL);
    }
    if(ret != 0) {
        OTA_LOG_E("rollback err:%d", ret);
    }
    return ret;
}

#ifndef OTA_DUBANK
OTA_WEAK int hal_reboot_bank(void)
{
    return 0;
}
#endif

OTA_WEAK int ota_hal_boot(ota_boot_param_t *param)
{
    int ret = OTA_UPGRADE_WRITE_FAIL;
    hal_logic_partition_t  ota_info;
    hal_logic_partition_t  app_info;
    hal_logic_partition_t *p_ota_info = &ota_info;
    hal_logic_partition_t *p_app_info = &app_info;
    memset(p_ota_info, 0, sizeof(hal_logic_partition_t));
    memset(p_app_info, 0, sizeof(hal_logic_partition_t));
    hal_flash_info_get(boot_part, p_ota_info);
    hal_flash_info_get(HAL_PARTITION_APPLICATION, p_app_info);
    if(param != NULL) {
        printf("ota_hal_boot  crc is 0x%x ", param->crc);
        ota_crc16_final(&ctx, &param->crc);
        param->src_adr = p_ota_info->partition_start_addr;
        param->len = (param->len < p_ota_info->partition_length)?param->len:p_ota_info->partition_length;
        param->dst_adr = p_app_info->partition_start_addr;
        param->old_size = p_app_info->partition_length;
        ret = ota_update_parameter(param);
        if(ret < 0) {
            return OTA_UPGRADE_WRITE_FAIL;
        }
        printf("OTA finish dst:0x%08x src:0x%08x len:0x%08x, crc:0x%04x param crc:0x%04x upg_flag:0x%04x \r\n", param->dst_adr, param->src_adr, param->len, param->crc, param->param_crc, param->upg_flag);
        if(param->upg_flag != OTA_UPGRADE_DIFF && param->upg_flag != OTA_UPGRADE_XZ)
            ret = hal_reboot_bank();
    }
    return ret;
}

OTA_WEAK int ota_hal_read(unsigned int *off, char *out_buf, unsigned int out_buf_len)
{
    int ret = 0;
    unsigned int offset = 0;
    hal_logic_partition_t  part_info = {0};
    if(off == NULL) {
         return OTA_UPGRADE_WRITE_FAIL;
    }
    hal_flash_info_get(boot_part, &part_info);
    if(*off + out_buf_len > part_info.partition_length) {
         offset = *off - part_info.partition_length;
         ret = ota_mcu_hal_read(&offset, (unsigned char *)out_buf, out_buf_len);
         *off += out_buf_len;
    } else {
         ret = hal_flash_read(boot_part, off, out_buf, out_buf_len);
    }
    if(ret < 0){
        return OTA_VERIFY_IMAGE_FAIL;
    }
    return ret;
}

OTA_WEAK const char *ota_hal_version(unsigned char dev_type, char* dn)
{
    if(dev_type > 0) {
        return "v1.0.0-20180101-1000";//SYSINFO_APP_VERSION;
    }
    else {
        return SYSINFO_APP_VERSION;
    }
}
